package android.slc.appbase.utils;


import android.slc.commonlibrary.TimeUtils;
import android.slc.utils.SlcFileUtils;
import android.slc.utils.SlcPathUtils;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.system.SystemUtil;
import io.reactivex.rxjava3.annotations.NonNull;
import org.apache.commons.lang3.StringUtils;

import java.io.File;

/**
 * <pre>
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/09/27
 *     desc  : utils about crash
 * </pre>
 */
public final class SlcCrashUtils {
    private static final String FILE_SEP = System.getProperty("file.separator");
    private static final Thread.UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();

    private SlcCrashUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * Initialization.
     */
    public static void init() {
        init("");
    }

    /**
     * Initialization
     *
     * @param crashDir The directory of saving crash information.
     */
    public static void init(@NonNull final File crashDir) {
        init(crashDir.getAbsolutePath(), null);
    }

    /**
     * Initialization
     *
     * @param crashDirPath The directory's path of saving crash information.
     */
    public static void init(final String crashDirPath) {
        init(crashDirPath, null);
    }

    /**
     * Initialization
     *
     * @param onCrashListener The crash listener.
     */
    public static void init(final OnCrashListener onCrashListener) {
        init("", onCrashListener);
    }

    /**
     * Initialization
     *
     * @param crashDir        The directory of saving crash information.
     * @param onCrashListener The crash listener.
     */
    public static void init(@NonNull final File crashDir, final OnCrashListener onCrashListener) {
        init(crashDir.getAbsolutePath(), onCrashListener);
    }

    /**
     * Initialization
     *
     * @param crashDirPath    The directory's path of saving crash information.
     * @param onCrashListener The crash listener.
     */
    public static void init(final String crashDirPath, final OnCrashListener onCrashListener) {
        String dirPath;
        if (StringUtils.isBlank(crashDirPath)) {
            dirPath = SlcPathUtils.getAppFileFilePath("crash");
            dirPath = dirPath.endsWith(FILE_SEP) ? dirPath : dirPath + FILE_SEP;
        } else {
            dirPath = crashDirPath.endsWith(FILE_SEP) ? crashDirPath : crashDirPath + FILE_SEP;
        }
        Thread.setDefaultUncaughtExceptionHandler(getUncaughtExceptionHandler(dirPath, onCrashListener));
    }

    private static Thread.UncaughtExceptionHandler getUncaughtExceptionHandler(final String dirPath,
                                                                               final OnCrashListener onCrashListener) {
        return new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(@NonNull final Thread t, @NonNull final Throwable e) {
                SlcCrashUtils.uncaughtException(dirPath, t, e, onCrashListener);
            }
        };
    }

    private static void uncaughtException(final String dirPath, @NonNull final Thread t, @NonNull final Throwable e, final OnCrashListener onCrashListener) {
        final String formatCrashInfo = formatCrashInfo(e);
        final OnNotifyUncaughtExceptionListener onNotifyUncaughtExceptionListener = new OnNotifyUncaughtExceptionListener() {
            @Override
            public void onNotifyUncaughtException(Throwable e) {
                final String crashFile = dirPath + SlcFileUtils.newFileNameByTime("CrashLog_", ".txt");
                FileUtil.appendString(formatCrashInfo, crashFile, CharsetUtil.CHARSET_UTF_8);
                if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
                    DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(t, e);
                } else {
                    e.printStackTrace();
                }
            }
        };
        if (onCrashListener != null) {
            onCrashListener.onCrash(formatCrashInfo(e), e, onNotifyUncaughtExceptionListener);
        } else {
            onNotifyUncaughtExceptionListener.onNotifyUncaughtException(e);
        }

    }

    public static String formatCrashInfo(@NonNull final Throwable e) {
        final StringBuilder sb = new StringBuilder();
        final String head = "************* Log Head ****************" +
                "\nTime Of Crash      : " + TimeUtils.getNowString() +
                "\nJAVA Version    : " + SystemUtil.getJvmInfo().getVersion() +
                "\n************* Log Head ****************\n\n";
        sb.append(head).append(ExceptionUtil.stacktraceToString(e));
        return sb.toString();
    }
    ///////////////////////////////////////////////////////////////////////////
    // interface
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 异常监听
     */
    public interface OnCrashListener {
        /**
         * 异常监听
         *
         * @param crashInfo                         异常信息
         * @param e                                 异常
         * @param onNotifyUncaughtExceptionListener 通知未处理异常监听
         */
        void onCrash(String crashInfo, Throwable e, OnNotifyUncaughtExceptionListener onNotifyUncaughtExceptionListener);
    }

    /**
     * 通知未处理异常监听
     */
    public interface OnNotifyUncaughtExceptionListener {
        void onNotifyUncaughtException(Throwable e);
    }
}
